package com.linking.third.party.manager;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.linking.third.party.pojo.bo.PaypalOriginBO;
import com.linking.third.party.pojo.bo.PaypalOriginPayBO;
import com.linking.third.party.pojo.bo.PaypalOriginPayResBO;
import com.linking.third.party.pojo.bo.ReceiptVerifyBO;
import com.paypal.core.PayPalEnvironment;
import com.paypal.core.PayPalHttpClient;
import com.paypal.http.HttpResponse;
import com.paypal.orders.AmountBreakdown;
import com.paypal.orders.AmountWithBreakdown;
import com.paypal.orders.ApplicationContext;
import com.paypal.orders.Money;
import com.paypal.orders.Order;
import com.paypal.orders.OrderRequest;
import com.paypal.orders.OrdersCaptureRequest;
import com.paypal.orders.OrdersCreateRequest;
import com.paypal.orders.PurchaseUnitRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

/**
 * @Author YaoWeiXin
 * @Date 2020/10/23 10:43
 * @Description PaypalOrigin管理类
 */
@Slf4j
public class PaypalOriginManager {

  private static Map<String, String> cacheToken = Maps.newHashMap();
  private static Map<String, PayPalHttpClient> cacheClient = Maps.newHashMap();

  private static final String TEST_TOKEN_URL = "https://api.sandbox.paypal.com/v1/oauth2/token";
  private static final String TEST_ORDER_CREATE = "https://api.sandbox.paypal.com//v1/checkout/orders/";
  private static final String TEST_PAYMENT_DETAIL = "https://api.sandbox.paypal.com/v1/payments/payment/";
  private static final String PROD_TOKEN_URL = "https://api.paypal.com/v1/oauth2/token";
  private static final String PROD_ORDER_CREATE = "https://api.paypal.com//v1/checkout/orders/";
  private static final String PROD_PAYMENT_DETAIL = "https://api.paypal.com/v1/payments/payment/";

  private static PayPalHttpClient getPaypalClient(String clientId, String secret, Boolean sendBox) {
    String key = clientId + ":" + sendBox;
    PayPalHttpClient client;
    if (cacheClient.containsKey(key)) {
      client = cacheClient.get(key);
    } else {
      if (sendBox) {
        PayPalEnvironment environment = new PayPalEnvironment.Sandbox(clientId, secret);
        client = new PayPalHttpClient(environment);
      } else {
        PayPalEnvironment environment = new PayPalEnvironment.Live(clientId, secret);
        client = new PayPalHttpClient(environment);
      }
      cacheClient.put(key, client);
    }
    return client;
  }

  /**
   * 创建支付
   *
   * @param paypalPay paypal支付参数
   * @return 创建结果
   */
  public static PaypalOriginPayResBO doPay(PaypalOriginPayBO paypalPay) {
    String clientId = paypalPay.getClientId();
    String secret = paypalPay.getSecret();
    boolean sendBox = paypalPay.getSendBox();
    Optional<String> opt = getAccessToken(clientId, secret, sendBox);
    if (!opt.isPresent()) {
      return PaypalOriginPayResBO.ofFail();
    }
    String token = opt.get();
    String url = sendBox ? TEST_ORDER_CREATE : PROD_ORDER_CREATE;
    Map<String, String> purchaseUnits = Maps.newHashMap();
    purchaseUnits.put("reference_id", paypalPay.getOrderId());
    purchaseUnits.put("amount", paypalPay.getAmount().toPlainString());

    Map<String, String> redirectUrls = Maps.newHashMap();
    redirectUrls.put("return_url", "https://example.com/return");
    redirectUrls.put("cancel_url", "https://example.com/return");

    Map<String, Object> params = Maps.newHashMap();
    params.put("purchase_units", purchaseUnits);
    params.put("redirect_urls", redirectUrls);
    String json = JSON.toJSONString(params);
    String res = HttpRequest.post(url)
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer " + token)
        .header("PayPal-Partner-Attribution-Id", clientId)
        .body(json)
        .execute().body();
    if (sendBox) {
      log.info("paypal origin getResponsePay res================{}", res);
    }
    if (StrUtil.isEmpty(res)) {
      return PaypalOriginPayResBO.ofFail();
    }
    return PaypalOriginPayResBO.ofFail();
  }

  /**
   * 创建支付
   *
   * @param paypalPay paypal支付参数
   * @return 创建结果
   */
  public static PaypalOriginPayResBO doPay2(PaypalOriginPayBO paypalPay) {
    String clientId = paypalPay.getClientId();
    String secret = paypalPay.getSecret();
    boolean sendBox = paypalPay.getSendBox();
    Order order;
    // Construct a request object and set desired parameters
    // Here, OrdersCreateRequest() creates a POST request to /v2/checkout/orders
    OrderRequest orderRequest = new OrderRequest();
    orderRequest.checkoutPaymentIntent("CAPTURE");
    List<PurchaseUnitRequest> purchaseUnits = new ArrayList<>();
    // 组合商品参数
    Money money = new Money().currencyCode("USD").value(paypalPay.getAmount().toPlainString());
    com.paypal.orders.Item item = new com.paypal.orders.Item();
    item.sku(paypalPay.getProductId());
    item.name(paypalPay.getProductDesc());
    item.quantity("1");
    item.unitAmount(money);
    List<com.paypal.orders.Item> items = Lists.newArrayList(item);
    AmountBreakdown amountBreakdown = new AmountBreakdown();
    amountBreakdown.itemTotal(money);
    purchaseUnits.add(new PurchaseUnitRequest()
        .customId(paypalPay.getOrderId())
        .items(items)
        .amountWithBreakdown(new AmountWithBreakdown().amountBreakdown(amountBreakdown).currencyCode("USD")
            .value(paypalPay.getAmount().toPlainString())));
    orderRequest.purchaseUnits(purchaseUnits);
    // 组合返回地址
    ApplicationContext applicationContext = new ApplicationContext();
    applicationContext.returnUrl(paypalPay.getReturnUrl());
    applicationContext.cancelUrl(paypalPay.getReturnUrl());
    orderRequest.applicationContext(applicationContext);
    OrdersCreateRequest request = new OrdersCreateRequest().requestBody(orderRequest);
    try {
      // Call API with your client and get a response for your call
      HttpResponse<Order> response = getPaypalClient(clientId, secret, sendBox).execute(request);
      // If call returns body in response, you can get the de-serialized version by
      // calling result() on the response
      order = response.result();
      Map<String, String> links = Maps.newHashMap();
      order.links().forEach(
          link -> links.put(link.rel(), link.href()));
      if (sendBox) {
        log.info("PaypalOrigin Order ID: {}, link：{}", order.id(), links.toString());
      }
      String approveLink = links.getOrDefault("approve", "");
      return PaypalOriginPayResBO.of(approveLink, order.id());
    } catch (IOException ioe) {
      log.error("PaypalOrigin do pay error: ", ioe);
      return PaypalOriginPayResBO.ofFail();
    }
  }

  /**
   * 票据验证
   *
   * @param clientId 客户编号
   * @param secret   秘钥
   * @param sendBox  环境
   * @param paypal   paypal支付对象
   * @return true|false
   */
  public static ReceiptVerifyBO receiptWebVerify(String clientId, String secret, Boolean sendBox,
      PaypalOriginBO paypal) {
    OrdersCaptureRequest request = new OrdersCaptureRequest(paypal.getPaypalOrderId());
    try {
      // Call API with your client and get a response for your call
      HttpResponse<Order> response = getPaypalClient(clientId, secret, sendBox).execute(request);
      // If call returns body in response, you can get the de-serialized version by
      // calling result() on the response
      Order order = response.result();
      String approved = "APPROVED";
      String completed = "COMPLETED";
      if (!approved.equals(order.status()) && !completed.equals(order.status())) {
        log.debug("PaypalOrigin receiptWebVerify fail status is fail============{}", order.status());
        // 验证未成功
        return ReceiptVerifyBO.ofFail();
      }
      return ReceiptVerifyBO.of(true, paypal.getPaypalOrderId(), paypal.getProductId());
    } catch (IOException ioe) {
      log.error("PaypalOrigin receiptWebVerify error: ", ioe);
      return ReceiptVerifyBO.ofFail();
    }
  }

  private static Optional<String> getAccessToken(String clientId, String secret, Boolean sendBox) {
    String key = clientId + ":" + sendBox;
    String token;
    if (cacheToken.containsKey(key)) {
      token = cacheToken.get(key);
    } else {
      String url = sendBox ? TEST_TOKEN_URL : PROD_TOKEN_URL;
      Map<String, Object> params = Maps.newHashMap();
      params.put("grant_type", "client_credentials");
      String authorization = "Basic " + Base64.encode((clientId + ":" + secret).getBytes());
      String paramStr = JSON.toJSONString(params);
      String res = HttpRequest.post(url)
          .header("Accept", "application/json")
          .header("Accept-Language", "en_US")
          .header("Authorization", authorization)
          .form(params)
          .execute().body();
      if (sendBox) {
        log.info("paypal origin authorization================{}", authorization);
      }
      if (sendBox) {
        log.info("paypal origin getAccessToken res================{},{},{},{},{}", url, clientId,
            secret,
            paramStr, res);
      }
      if (StrUtil.isEmpty(res)) {
        return Optional.empty();
      }
      JSONObject json = JSON.parseObject(res);
      token = json.getString("access_token");
      if (StrUtil.isNotEmpty(token)) {
        cacheToken.put(key, token);
      }
    }
    return Optional.ofNullable(token);
  }

  private static Optional<ResponsePay> getResponsePay(String token, boolean sendBox,
      String paymentId) {
    String url = sendBox ? TEST_PAYMENT_DETAIL : PROD_PAYMENT_DETAIL;
    String res = HttpRequest.get(url + paymentId)
        .header("Accept", "application/json")
        .header("Authorization", "Bearer " + token)
        .execute().body();
    if (sendBox) {
      log.info("paypal origin getResponsePay res================{}", res);
    }
    if (StrUtil.isEmpty(res)) {
      return Optional.empty();
    }
    return Optional.of(JSON.parseObject(res, ResponsePay.class));
  }

  public static String getOrderNumber(ResponsePay responsePay) {
    if (responsePay.getTransactions() == null || responsePay.getTransactions().isEmpty()) {
      return null;
    }
    Transaction transaction = responsePay.getTransactions().get(0);
    if (transaction.getItemList() == null || transaction.getItemList().getItems() == null
        || transaction.getItemList().getItems().isEmpty()) {
      return null;
    }
    return transaction.getItemList().getItems().get(9).getSku();
  }

  /**
   * 票据验证
   *
   * @param clientId 客户编号
   * @param secret   秘钥
   * @param sendBox  环境
   * @param paypal   paypal支付对象
   * @return true|false
   */
  public static ReceiptVerifyBO receiptVerify(String clientId, String secret, Boolean sendBox,
      PaypalOriginBO paypal) {
    try {
      Optional<String> opt = getAccessToken(clientId, secret, sendBox);
      if (opt.isPresent()) {
        return receiptVerify(opt.get(), sendBox, paypal);
      }
      return ReceiptVerifyBO.ofFail();
    } catch (Exception e) {
      log.info("Paypal origin receiptVerify failure = {0}", e);
      return ReceiptVerifyBO.ofFail();
    }
  }

  private static ReceiptVerifyBO receiptVerify(String accessToken, Boolean sendBox,
      PaypalOriginBO paypal) {
    if (sendBox) {
      log.debug("receiptVerify============{},{}", paypal.getAmount(), paypal.getPaymentId());
    }
    Optional<ResponsePay> opt = getResponsePay(accessToken, sendBox, paypal.getPaymentId());
    if (!opt.isPresent()) {
      log.debug("receiptVerify fail responsePay is null============");
      // 验证未成功
      return ReceiptVerifyBO.ofFail();
    }
    ResponsePay responsePay = opt.get();
    String approved = "approved";
    String completed = "completed";
    if (!approved.equals(responsePay.getState()) && !completed.equals(responsePay.getState())) {
      log.debug("receiptVerify fail state is fail============{}", responsePay.getState());
      // 验证未成功
      return ReceiptVerifyBO.ofFail();
    }
    String orderId = getOrderNumber(responsePay);
    return ReceiptVerifyBO.of(true, orderId, paypal.getProductId());
  }

  @Data
  public static class ResponsePay {

    private String id;
    private String intent;
    private String state;
    private String cart;
    private Payer payer;
    private ArrayList<Transaction> transactions;
    private String createTime;
    private ArrayList<Link> links;
  }

  @Data
  public static class Transaction {

    private Amount amount;
    private Payee payee;
    private String description;
    private ItemList itemList;
    private ArrayList<RelatedResource> relatedResources;
  }

  @Data
  public static class RelatedResource {

    private Sale sale;
  }

  @Data
  public static class Sale {

    private String id;
    private String state;
    private Amount amount;
    private String paymentMode;
    private String protectionEligibility;
    private String protectionEligibilityType;
    private TransactionFee transactionFee;
    private String parentPayment;
    private String createTime;
    private String updateTime;
    private ArrayList<Link> links;
  }

  @Data
  public static class TransactionFee {

    private String value;
    private String currency;
  }

  @Data
  public static class ItemList {

    private ArrayList<Item> items;
    private ShippingAddress shippingAddress;
  }

  @Data
  public static class Item {

    private String name;
    private String sku;
    private String price;
    private String currency;
    private String tax;
    private int quantity;
  }

  @Data
  public static class Payee {

    private String merchantId;
  }

  @Data
  public static class Amount {

    private String total;
    private String currency;
    private Details details;
  }

  @Data
  public static class Details {

    private String subtotal;
  }

  @Data
  public static class Link {

    private String links;
    private String rel;
    private String method;
  }

  @Data
  public static class Payer {

    private String paymentMethod;
    private String status;
    private PayerInfo payerInfo;
  }

  @Data
  public static class PayerInfo {

    private String email;
    private String firstName;
    private String lastName;
    private String payerId;
    private ShippingAddress shippingAddress;
    private String phone;
    private String countryCode;
  }

  @Data
  public static class ShippingAddress {

    private String recipientName;
  }
}
